결정 트리(Decision Tree)

In [ ]:
!pip install mglearn
!pip install --upgrade joblib==1.1.0
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: mglearn in /usr/local/lib/python3.7/dist-packages (0.1.9)
Requirement already satisfied: scikit-learn in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.0.2)
Requirement already satisfied: imageio in /usr/local/lib/python3.7/dist-packages (from mglearn) (2.9.0)
Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.3.5)
Requirement already satisfied: pillow in /usr/local/lib/python3.7/dist-packages (from mglearn) (7.1.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.21.6)
Requirement already satisfied: joblib in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.1.0)
Requirement already satisfied: cycler in /usr/local/lib/python3.7/dist-packages (from mglearn) (0.11.0)
Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/dist-packages (from mglearn) (3.2.2)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mglearn) (3.0.9)
Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mglearn) (2.8.2)
Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mglearn) (1.4.4)
Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from kiwisolver>=1.0.1->matplotlib->mglearn) (4.1.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->matplotlib->mglearn) (1.15.0)
Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/dist-packages (from pandas->mglearn) (2022.4)
Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->mglearn) (3.1.0)
Requirement already satisfied: scipy>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->mglearn) (1.7.3)
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/
Requirement already satisfied: joblib==1.1.0 in /usr/local/lib/python3.7/dist-packages (1.1.0)

지니 불순도 (Gini Impurity)¶

지니 불순도는 결정 트리의 분할기준 중 하나입니다.

지니 불순도를 찾기 위해서는 1에서 시작해서 세트의 각 class 비율의 제곱을 빼면 됩니다.

$$\text{Gini Impurity} = 1 - \text{Gini Index} \\ = 1 - \sum_{i=1}^{K}p_{i}^{2}$$

위 식에서 $K$은 class label의 개수이며, $p_i$은 $i$번째 class label의 비율입니다.

예를 들어, A class인 instance가 3개 있고 B class인 instance가 1개 있는 데이터의 경우에는 지니 불순도는 아래와 같이 계산됩니다.

$$1 - (3/4)^2 - (1/4)^2 = 0.375$$

만약 데이터가 하나의 class만 있다면, 지니 불순도는 0이 됩니다. 불순도가 낮으면 낮을수록 결정 트리의 성능은 더 좋아집니다.

실습 1¶

위 정리에서 주어진 Tree의 불순도 계산

In [ ]:
1 - (4/6)**2 - (2/6)**2
Out[ ]:
0.4444444444444445

sample_labels 리스트의 지니 불순도 계산

In [ ]:
sample_labels = ["unacc", "unacc", "acc", "acc", "good", "good"]
impurity = 1

sample labels에 포함되어있는 class의 개수

In [ ]:
from collections import Counter
label_counts = Counter(sample_labels)
print(label_counts)
Counter({'unacc': 2, 'acc': 2, 'good': 2})

데이터셋에서 각 label의 확률 계산

In [ ]:
for label in label_counts:
  print(label)
  prob = label_counts[label]/len(sample_labels)
  print(prob)
unacc
0.3333333333333333
acc
0.3333333333333333
good
0.3333333333333333

확률을 이용하여 sample_labels의 불순도 계산

In [ ]:
for label in label_counts:
  prob = label_counts[label]/len(sample_labels)
  impurity -= prob ** 2
print(impurity)
0.6666666666666665

지니 불순도를 계산하는 코드 함수 제작

In [ ]:
def gini(dataset):
  impurity = 1
  label_counts =Counter(dataset)
  for label in label_counts:
    prob_of_label = label_counts[label] / len(dataset)
    impurity -= prob_of_label ** 2
  return impurity

정보증가량 (Information Gain)¶

이제 지니 불순도가 낮은 끝마디(leaf node)를 만들기 위해서 어떠한 feature에 따라 데이터를 나누어야하는지 결정해야 합니다.

예를 들어, 학생들의 수면 시간 또는 학생들의 공부 시간 둘 중 어느 feature을 기준으로 학생들을 나누어야 더 좋은 tree를 만들 수 있을까요?

위 질문에 답하기 위해 어떠한 feature에 대하여 데이터를 나누었을 때의 정보증가량을 계산해야 합니다.

정보증가량은 데이터 분할 전과 후의 불순도 차이를 측정합니다.

예를 들어, 불순도가 0.5인 데이터를 어떠한 feature에 대해 나누었을 때, 불순도가 각각 0, 0.375, 0 인 끝마디가 생긴다고 가정해봅니다.

이 경우에 데이터를 나누는 정보증가량은 0.5 - 0 - 0.375 - 0 = 0.125 입니다.

데이터를 나누었을때의 정보 증가량은 양수입니다. 따라서, 위처럼 결정 지점을 나눈 것은 결과적으로 불순도를 낮추었기 때문에 좋은 결정 지점입니다.

정보증가량은 크면 클수록 좋습니다.

실습 2¶

unsplit_labels라는 임의의 데이터를 두가지 다른 분할 지점으로 나누었습니다. 이는 split_labels_1와 split_labels_2 입니다.

각 분할에 대해 information gain 계산

In [ ]:
unsplit_labels = ["unacc", "unacc", "unacc", "unacc", "unacc", "unacc", "good", 
                  "good", "good", "good", "vgood", "vgood", "vgood"]

split_labels_1 = [["unacc", "unacc", "unacc", "unacc", "unacc", "unacc", "good", "good", "vgood"], 
                  [ "good", "good"], 
                  ["vgood", "vgood"]]

split_labels_2 = [["unacc", "unacc", "unacc", "unacc","unacc", "unacc", "good", "good", "good", "good"], 
                  ["vgood", "vgood", "vgood"]]
In [ ]:
# unsplit_labels의 지니 불순도를 계산해봅니다.
info_gain_1 = gini(unsplit_labels)
info_gain_1
Out[ ]:
0.6390532544378698

split_labels_1의 각 부분집합에 대하여 지니 불순도을 계산하여 정보 증가량 계산

In [ ]:
for subset in split_labels_1:
  info_gain_1 -= gini(subset)
print(info_gain_1)
0.14522609394404257

split_labels_2에 대해 정보증가량을 계산

In [ ]:
info_gain_2 = gini(unsplit_labels)
for subset in split_labels_2:
  info_gain_2 -= gini(subset)
print(info_gain_2)
0.15905325443786977

정보증가량을 계산하는 함수

In [ ]:
def information_gain(starting_labels, split_labels):
  info_gain = gini(starting_labels)
  for subset in split_labels:
   info_gain -= gini(subset)
  return info_gain

가중 정보증가량 (Weighted Information Gain)¶

만약 정보증가량이 0이라면 그 feature에 대해 데이터를 나누는 것은 소용이 없습니다. 때에 따라서 데이터를 나누었을 때 정보증가량이 음수가 될 수 있습니다. 이 문제를 해결하기 위해서 가중 정보증가량 (weighted information gain)을 사용합니다.

분할 후에 생성되는 데이터의 부분집합의 크기 또한 중요합니다. 예를 들어서, 아래 이미지에서는 불순도가 같은 두 부분집합이 있습니다.

어느 부분집합을 결정 트리의 끝마디로 정하는게 좋은 결정트리를 만들 수 있을까요?

두 부분집합은 모두 불순도가 0으로써 완전하지만, 두 번째 부분집합이 더욱 의미있습니다. 두 번째 부분집합에는 많은 개수의 instance들이 있기 때문에 이 부분집합이 구성된 것이 우연이 아님을 알수 있습니다.

그 반대를 생각해보는 것도 도움이 됩니다. 아래 그림에서 같은 값의 불순도를 가지고 있는 두 부분집합이 있습니다.

이 두 부분집합의 불순도는 굉장히 높습니다. 그렇지만 어느 부분집합의 불순도가 더 큰 의미를 가질까요? 왼쪽의 부분집합을 분할하는 것보다는 오른쪽 부분집합을 분할하여 불순도가 없는 집합을 만드는 것이 정보증가량이 더 클 것입니다. 따라서, 집합의 instance 개수를 고려하여 정보증가량을 계산해야 합니다.

집합의 크기까지 고려하도록 정보증가량 함수를 수정할 것 입니다. 단순히 불순도를 빼는 것에서 더 나아가 분할된 부분집합의 가중 불순도를 뺄 것입니다. 만약 분할 전의 데이터가 10개의 instance을 가지고 있고 하나의 부분집합이 2개의 instance가 있다면, 그 부분집합의 가중 불순도는 2/10 * impurity가 되어 instance 숫자가 적은 세트의 중요도를 낮춥니다.

가중 정보증가량 계산의 예시는 아래와 같습니다.

실습 3¶

아래는 데이터셋의 각 feature와 class label에 대한 설명입니다. Car dataset은 class에 해당하는 4가지 label과 각 차량의 특징을 나타내는 6개의 feature을 갖고 있습니다.

Label은 4개의 class, unacc(unacceptable), acc(acceptable), good, vgood로 이루어져 있으며, 각 class는 차량 구매시의 만족도(acceptability)를 나타냅니다.

각 차량은 6개의 feature을 가지고 있고, 아래와 같습니다.

  • buying (차량의 가격): "vhigh","high","med", or "low".
  • maint (차량 유지 비용): "vhigh","high","med", or "low".
  • doors (차의 문 갯수): "2","3","4","5more".
  • persons (차량의 최대 탑승 인원): "2","4", or "more".
  • lug_boot (차량 트렁크의 사이즈): "small","med", or "big"
  • safety (차량의 안전성 등급): "low","med", or "high".
In [ ]:
# 샘플 데이터
cars = [['med', 'low', '3', '4', 'med', 'med'], 
        ['med', 'vhigh', '4', 'more', 'small', 'high'], 
        ['high', 'med', '3', '2', 'med', 'low'], 
        ['med', 'low', '4', '4', 'med', 'low'], 
        ['med', 'low', '5more', '2', 'big', 'med'],
        ['med', 'med', '2', 'more', 'big', 'high'],
        ['med', 'med', '2', 'more', 'med', 'med'],
        ['vhigh', 'vhigh', '2', '2', 'med', 'low'], 
        ['high', 'med', '4', '2', 'big', 'low'], 
        ['low', 'low', '2', '4', 'big', 'med']]

car_labels = ['acc', 'acc', 'unacc', 'unacc', 'unacc', 'vgood', 'acc', 'unacc', 'unacc', 'good']

information_gain 함수를 수정하여 가중 정보증가량을 계산

가중치: 부분집합의 label 갯수 `len(subset)` / 분할 전의 집합의 label 갯수 `len(starting_labels)`
In [ ]:
def weighted_information_gain(starting_labels, split_labels):
  info_gain = gini(starting_labels)
  for subset in split_labels:
   info_gain -= gini(subset) * (len(subset) / len(starting_labels))
  return info_gain
  1. 아래 split() 함수를 살펴보겠습니다.
In [ ]:
def split(dataset, labels, column): 
    data_subsets = []
    label_subsets = [] 
    # empty list
    counts = list(set([data[column] for data in dataset]))
    # list 의 중복 항목 제거를 위한 set 변환
    for k in counts: # k=counts element ['2', '4', 'more']
        new_data_subset = [] 
        new_label_subset = []
        for i in range(len(dataset)): # data set len -> all looping
            if dataset[i][column] == k: 
                new_data_subset.append(dataset[i])
                new_label_subset.append(labels[i])
        data_subsets.append(new_data_subset)
        label_subsets.append(new_label_subset) 
    return data_subsets, label_subsets 
In [ ]:
# split 함수 호출
split_data, split_labels = split(cars, car_labels, 3)
In [ ]:
split_data
Out[ ]:
[[['med', 'vhigh', '4', 'more', 'small', 'high'],
  ['med', 'med', '2', 'more', 'big', 'high'],
  ['med', 'med', '2', 'more', 'med', 'med']],
 [['med', 'low', '3', '4', 'med', 'med'],
  ['med', 'low', '4', '4', 'med', 'low'],
  ['low', 'low', '2', '4', 'big', 'med']],
 [['high', 'med', '3', '2', 'med', 'low'],
  ['med', 'low', '5more', '2', 'big', 'med'],
  ['vhigh', 'vhigh', '2', '2', 'med', 'low'],
  ['high', 'med', '4', '2', 'big', 'low']]]
In [ ]:
len(split_data)
Out[ ]:
3

split_labels를 사용, index 3에 대해 스플릿한 information gain

In [ ]:
# index 3으로 데이터를 분할하였을 때 정보증가량을 출력
weighted_information_gain(car_labels, split_labels)
Out[ ]:
0.30666666666666675

정보증가량을 찾는 과정을 모든 feature에 대해서 적용

In [ ]:
# 데이터에 있는 모든 feature들에 대하여 `split()` 함수와 `information_gain()` 함수를 호출 
# 4th feature(persons feature)가 가장 큰 영향을 미친다.
for i in range(0,6):
  split_data, split_labels = split(cars, car_labels, i)
  print(weighted_information_gain(car_labels, split_labels))
0.2733333333333334
0.040000000000000036
0.10666666666666666
0.30666666666666675
0.15000000000000002
0.29000000000000004

실습 4: 재귀 트리 만들기 (Recursive Tree Building)¶

데이터를 분할하였을 때 정보증가량이 가장 높은 feature을 찾을 수 있습니다. 이 방법을 반복하는 재귀 알고리즘을 통하여 트리를 구성할 수 있습니다. 데이터의 모든 instance에서 시작하여 데이터를 분할할 가장 좋은 feature을 찾고, 그 feature에 대해서 데이터를 나눈 후에 생성된 부분집합에 대해서 재귀적으로 위의 순서를 되풀이합니다.

정보증가량이 일어나지 않는 feature을 찾을 때까지 재귀를 반복합니다. 다른 말로, 우리는 더이상 불순도가 없는 부분집합을 만드는 분할이 존재하지 않을 때 결정 트리의 끝마디를 생성합니다. 이 끝마디는 전체 데이터에서 분류된 instance의 class을 담고 있습니다.

In [ ]:
# 위의 함수들을 종합하여 가장 적합한 분할 feature을 찾는 함수 작성
def find_best_split(dataset, labels):
  best_gain = 0
  best_feature = 0
  for feature in range(len(dataset[0])):
    data_subset, label_subset = split(dataset, labels, feature)
    gain = weighted_information_gain(labels, label_subset)
    if gain > best_gain:
      best_gain, best_feature = gain, feature
  return best_gain, best_feature

위 함수를 cars와 car_labels에 대해 호출

In [ ]:
best_gain, best_feature = find_best_split(cars,  car_labels)
In [ ]:
best_feature
Out[ ]:
3
In [ ]:
best_gain
Out[ ]:
0.30666666666666675

data와 labels를 파라미터로 받는 build_tree()라는 함수를 선언

이 함수는 재귀적으로 트리를 구성합니다.

In [ ]:
def build_tree(data, labels):
  best_gain, best_feature = find_best_split(data,  labels)
  if best_gain == 0:
    return Counter(labels)
  data_subsets, label_subsets = split(data,  labels, best_feature)
  branches = []
  for i in range(len(data_subsets)):
    branch = build_tree(data_subsets[i], label_subsets[i])
    branches.append(branch)
  return branches

만들어진 build_tree 함수 테스트

In [ ]:
def print_tree(node, spacing=""):
    question_dict = {0: "Buying Price", 1:"Price of maintenance", 
                     2:"Number of doors", 3:"Person Capacity", 
                     4:"Size of luggage boot", 5:"Estimated Saftey"}
    # Base case: 끝노드에 도달함
    if isinstance(node, Counter):
        print (spacing + str(node))
        return

    print (spacing + "Splitting")

    # 분할 지점에서 각 브랜치에 대해 재귀적으로 print_tree 함수를 호출
    for i in range(len(node)):
        print (spacing + '--> Branch ' + str(i)+':')
        print_tree(node[i], spacing + "  ")
In [ ]:
#  `build_tree` 함수와 `print_tree` 함수를 출력해봅니다.
tree = build_tree(cars, car_labels)
print_tree(tree)
Splitting
--> Branch 0:
  Splitting
  --> Branch 0:
    Counter({'acc': 1})
  --> Branch 1:
    Counter({'acc': 1})
  --> Branch 2:
    Counter({'vgood': 1})
--> Branch 1:
  Splitting
  --> Branch 0:
    Counter({'unacc': 1})
  --> Branch 1:
    Counter({'good': 1})
  --> Branch 2:
    Counter({'acc': 1})
--> Branch 2:
  Counter({'unacc': 4})

실습 5: scikit-learn으로 구현하는 결정트리¶

scikit-learn에서 제공되는 make_moons 데이터를 사용합니다.

In [ ]:
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_moons

X, y = make_moons(noise=0.32, random_state=42, n_samples=250)
sns.scatterplot(x=X[:, 0], y=X[:, 1], 
                hue=y, marker="o", s=25, 
                edgecolor="k", legend=False).set_title("Moon Data")
plt.show()

scikit-learn 패키지로 결정트리 구현

DecisionTreeClassifier` 분류기를 사용합니다.

[scikit-learn DecisionTreeClassifier documentation] (https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html?highlight=decisiontreeclassifier#sklearn.tree.DecisionTreeClassifier)

In [ ]:
from sklearn.tree import DecisionTreeClassifier
In [ ]:
dt = DecisionTreeClassifier()

.fit() 메소드를 통해 데이터를 Tree에 훈련

In [ ]:
dt.fit(X,y)
Out[ ]:
DecisionTreeClassifier()

정확도 확인

In [ ]:
dt.score(X,y)
Out[ ]:
1.0

완성된 결정트리 시각화

In [ ]:
# classifier 결정트리를 시각화
from sklearn.tree import export_graphviz # drawing graphs specified in DOT language scripts
from six import StringIO
from IPython.display import Image  
import pydotplus

dot_data = StringIO()
export_graphviz(dt, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:

결정 경계 시각화

In [ ]:
from mglearn import plot_interactive_tree

ax = plot_interactive_tree.plot_tree_partition(X, y, dt)
ax.set_title("first decision tree")
Out[ ]:
Text(0.5, 1.0, 'first decision tree')

실습 6: 결정 트리 가지치기 (pruning)¶

main-qimg-041ce957cef0af970c14e3419163c17c.png

가지치기란 최대트리로 형성된 결정트리의 특정 노드 밑의 하부 트리를 제거하여 일반화 성능을 높히는 것을 의미합니다. 모든 끝노드의 불순도가 0인 트리를 full tree라고 하는데, 이 경우에는 분할이 너무 많이 과적합의 위험이 발생합니다. 과적합은 학습 데이터에 과하게 학습하여 실제 데이터에 오차가 증가하는 현상입니다. 이를 방지하기 위해서 적절한 수준에서 끝노드를 결합해주는 기법을 가지치기(pruning)이라고 합니다.

scikit-learn DecisionTreeClassifier documentation

새로운 결정트리를 생성 (깊이를 지정)

In [ ]:
pruned_dt = DecisionTreeClassifier(max_depth = 3)
pruned_dt.fit(X, y)
print(pruned_dt.score(X,y))
0.888

가지치기된 트리를 시각화

이렇게 끝노드의 개수를 지정해주면 트리가 데이터에 더욱 잘 일반화됩니다.
In [ ]:
dot_data = StringIO()
export_graphviz( pruned_dt, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:

결정 경계 시각화를 통한 비교

In [ ]:
ax = plot_interactive_tree.plot_tree_partition(X, y, pruned_dt)
ax.set_title("first decision tree")
Out[ ]:
Text(0.5, 1.0, 'first decision tree')

과제¶

Kaggle의 Titanic 데이터를 사용

타이타닉 데이터의 feature:

  • Pclass: 승객 등급. 1등급=1, 2등급=2, 3등급=3
  • Sex: 성별
  • Age: 나이
  • SibSp: 함께 탑승한 형제 또는 배우자 수
  • Parch: 함께 탑승한 부모 또는 자녀 수
  • Fare: 여객 운임

Label: Survived 생존=1, 죽음=0

  1. 데이터 파악 및 전처리
In [ ]:
import pandas as pd

data_url = "https://raw.githubusercontent.com/inikoreaackr/ml_datasets/main/titanic.csv"
data = pd.read_csv(data_url)
data
Out[ ]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
... ... ... ... ... ... ... ... ... ... ... ... ...
886 887 0 2 Montvila, Rev. Juozas male 27.0 0 0 211536 13.0000 NaN S
887 888 1 1 Graham, Miss. Margaret Edith female 19.0 0 0 112053 30.0000 B42 S
888 889 0 3 Johnston, Miss. Catherine Helen "Carrie" female NaN 1 2 W./C. 6607 23.4500 NaN S
889 890 1 1 Behr, Mr. Karl Howell male 26.0 0 0 111369 30.0000 C148 C
890 891 0 3 Dooley, Mr. Patrick male 32.0 0 0 370376 7.7500 NaN Q

891 rows × 12 columns

In [ ]:
data.columns

data = data[['Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare']]

data.describe()
Out[ ]:
Survived Pclass Age SibSp Parch Fare
count 891.000000 891.000000 714.000000 891.000000 891.000000 891.000000
mean 0.383838 2.308642 29.699118 0.523008 0.381594 32.204208
std 0.486592 0.836071 14.526497 1.102743 0.806057 49.693429
min 0.000000 1.000000 0.420000 0.000000 0.000000 0.000000
25% 0.000000 2.000000 20.125000 0.000000 0.000000 7.910400
50% 0.000000 3.000000 28.000000 0.000000 0.000000 14.454200
75% 1.000000 3.000000 38.000000 1.000000 0.000000 31.000000
max 1.000000 3.000000 80.000000 8.000000 6.000000 512.329200
In [ ]:
data = data.dropna()
In [ ]:
data.shape
Out[ ]:
(714, 7)
In [ ]:
data.dtypes
Out[ ]:
Survived      int64
Pclass        int64
Sex          object
Age         float64
SibSp         int64
Parch         int64
Fare        float64
dtype: object
In [ ]:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
data.Sex = le.fit_transform(data.Sex)
/usr/local/lib/python3.7/dist-packages/pandas/core/generic.py:5516: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  self[name] = value
In [ ]:
data.dtypes
Out[ ]:
Survived      int64
Pclass        int64
Sex           int64
Age         float64
SibSp         int64
Parch         int64
Fare        float64
dtype: object
In [ ]:
y = data['Survived']
X = data.drop(columns = ['Survived'])

기계학습 모델을 훈련시키고 성능을 파악하기 위해서는 데이터를 훈련 데이터와 테스트 데이터로 나누어야 합니다.

scikit-learn에서 지원하는 train_test_split 을 사용합니다. scikit-learn train_test_split documentation

In [ ]:
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)
  1. DecisionTreeClassifier 분류기를 사용해 결정트리를 생성
In [ ]:
dt = DecisionTreeClassifier()
  1. 트레이팅 데이터에 .fit() 메소드를 호출함으로써 트리를 데이터에 훈련, .fit()메소드는 training_points와 training_labels을 파라미터로 받음.
In [ ]:
dt.fit(X_train, y_train)
Out[ ]:
DecisionTreeClassifier()
  1. testing_points와 testing_labels에 대한 결정 트리의 정확도(.score())를 출력
In [ ]:
print(dt.score(X_test, y_test))
0.8046511627906977
  1. 훈련된 트리 시각화
In [ ]:
dot_data = StringIO()
export_graphviz(dt, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:

scikit-learn DecisionTreeClassifier documentation

과제 1

  1. max_leaf_nodes : 트리가 가질 수 있는 최대 leaf node의 수를 지정하는 parameter입니다. 해당 parameter가 지정되지 않은 경우 leaf node 수의 제한을 두지 않습니다.

  2. max_depth : 트리의 최대 깊이 parameter가 None일 때는 node가 모든 잎들이 pure해질 때까지 혹은, min_samples_split 이 지정하는 값 이하의 수를 포함하도록 확장됩니다.

  3. min_sample_split : node를 분할하기 위해 필요한 최소 샘플 수를 지정하는 parameter로 정수일 때는 최소 숫자로 간주되고 float일 때는 전체 샘플에 대한 min_sample_split의 비율만큼을 최소 샘플로 합니다.

  4. min_sample_leaf : leaf nodes에 필요한 최소 샘플의 수르 지정하는 parameter로 양쪽 가지에 필요한 최소 training sample을 의미합니다. 특히, regression에서는 모형을 smoothing하는데 효과가 있습니다.

  5. min_impurity_decrease : impurity가 감소하는 경우 node를 분할하도록 합니다.

In [ ]:
from sklearn.tree import DecisionTreeClassifier
df = DecisionTreeClassifier()
In [ ]:
dt_mxlf = DecisionTreeClassifier(max_leaf_nodes = 1000)
dt_mxdth = DecisionTreeClassifier(max_depth = 6)
dt_mss = DecisionTreeClassifier(min_samples_split = 8)
dt_msl = DecisionTreeClassifier(min_samples_leaf = 32) 
dt_mid = DecisionTreeClassifier(min_impurity_decrease = 0.02)
In [ ]:
# prunig parameter를 조절하지 않은 Decision tree
dt.fit(X_train, y_train)
dt.score(X_test, y_test)
Out[ ]:
0.8232558139534883
In [ ]:
# `max_leaf_nodes` parameter를 조절한 Decision tree
dt_mxlf.fit(X_train, y_train)
dt_mxlf.score(X_test, y_test)
Out[ ]:
0.8186046511627907
In [ ]:
# `max_depth` parameter를 조절한 Decision tree
dt_mxdth.fit(X_train, y_train)
dt_mxdth.score(X_test, y_test)
Out[ ]:
0.8372093023255814
In [ ]:
#`min_sample_split` parameter를 조절한 Decision tree
dt_mss.fit(X_train, y_train)
dt_mss.score(X_test, y_test)
Out[ ]:
0.8
In [ ]:
#`min_sample_leaf` parameter를 조절한 Decision tree
dt_msl.fit(X_train, y_train)
dt_msl.score(X_test, y_test)
Out[ ]:
0.827906976744186
In [ ]:
#`min_impurity_decrease` parameter를 조절한 Decision tree
dt_mid.fit(X_train, y_train)
dt_mid.score(X_test, y_test)
Out[ ]:
0.8

과제3

  • prunig 파라미터를 조정하지 않은 Decision tree의 경우 분류 정확도가 [0.7488372093023256]로 pruning 파라미터를 조정한 Decision tree의 분류 정확도가 각각 [0.7813953488372093, 0.8, 0.7813953488372093, 0.813953488372093, 0.7813953488372093] 인 것과 비교해 볼 때 정확도가 떨어집니다.
In [ ]:
# `max_leaf_nodes` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mxlf, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:
In [ ]:
# `max_depth` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mxdth, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:
In [ ]:
#`min_sample_split` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mss, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:
In [ ]:
#`min_sample_leaf` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_msl, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:
In [ ]:
#`min_impurity_decrease` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mid, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
Out[ ]:
  • 과제 5번

max_leaf_nodes, max_depth, min_samples_splitparameter를 조정한 모델은 모두 트리 모델의 끝마디에서 Gini index는 높으나 node가 가지고 있는 sample의 수가 1 또는 2로 overfitting 되었을 확률이 높은 노드들을 다수 가지고 있으므로 적합하게 학습된 모델이라 할 수 없습니다. 한편 min_impurity_decrease parameter를 조절한 Decision tree 모델의 경우에는 tree 의 끝마디 noder가 가진 gini index가 다소 높게 측정되었기 때문에 적합되었다고 보기 어렵습니다. 반면 'min_samples_leaf` parameter를 조절한 Decision tree는 하나의 끝마디에서 gini index가 다소 높게 측정되기는 했지만 각각의 끝마디 gini index가 낮게 나왔으며 각 끝마디의 샘플 수도 적정하기 때문에 다른 parameter를 조절한 Decision tree에 비교해 가장 적합하게, 가장 일반화를 잘 실행한 모델이라 볼 수 있을 것입니다.